package token

import (
	"fmt"
	"reflect"
	"sync"
	"time"

	"starsine.cn/appTpl/cmn"
	"starsine.cn/appTpl/cmn/logs"
	"starsine.cn/appTpl/cmn/timermgr"
	"starsine.cn/appTpl/cmn/utils"
)

type TokenInfo struct {
	Token  string
	Expire int64
}

func (t TokenInfo) String() string {
	return fmt.Sprintf("token: %s, expire: %s", t.Token, time.UnixMicro(t.Expire/int64(time.Microsecond)).String())
}

func (p *TokenInfo) valid() bool {
	return p.Expire >= time.Now().UnixNano()
}

type UserInfo struct {
	UserName string
}

func (t UserInfo) String() string {
	return fmt.Sprintf("user: %s", t.UserName)
}

type TokenMap struct {
	// token -> user
	t2u map[string]UserInfo
	// user -> token
	u2t map[UserInfo]TokenInfo

	sync.RWMutex
	tm *timermgr.TimerMgr
}

func CreateTokenMap() *TokenMap {
	p := new(TokenMap)

	p.t2u = make(map[string]UserInfo)
	p.u2t = make(map[UserInfo]TokenInfo)

	p.tm = cmn.GetTm()

	return p
}

// vKey: user
func (p *TokenMap) CreateToken(userInfo UserInfo) TokenInfo {
	p.RWMutex.Lock()
	defer p.RWMutex.Unlock()

	if v, ok := p.u2t[userInfo]; ok {
		if v.valid() {
			return v
		} else {
			delete(p.u2t, userInfo)
			delete(p.t2u, v.Token)
		}
	}

	token := utils.GenerateUUID()
	tokenInfo := TokenInfo{
		Token:  token,
		Expire: time.Now().UnixNano() + int64(24*time.Hour),
		// Expire: time.Now().UnixNano() + int64(3*time.Second),
	}
	p.u2t[userInfo] = tokenInfo

	if v, ok := p.t2u[token]; ok {
		delete(p.t2u, token)
		if v != userInfo {
			logs.Infof("token risk.")
		}
	}
	p.t2u[token] = userInfo

	p.setTokenTimeout(tokenInfo)
	return tokenInfo
}

// vKey: token
func (p *TokenMap) RemoveToken(token string) {
	p.RWMutex.Lock()
	defer p.RWMutex.Unlock()

	if v, ok := p.t2u[token]; ok {
		delete(p.t2u, token)
		delete(p.u2t, v)
	}
}

func (p *TokenMap) setTokenTimeout(tokenInfo TokenInfo) {
	p.tm.AddAfterTimer(
		time.Duration(tokenInfo.Expire-time.Now().UnixNano())/time.Second,
		timermgr.NewOnTimeOut(p.expire,
			&tokenInfo,
		))
}

func (p *TokenMap) expire(params interface{}) {
	args := reflect.ValueOf(params)
	if args.Len() == 1 {
		tokenInfo := *(args.Index(0).Interface()).(*TokenInfo)
		p.RemoveToken(tokenInfo.Token)
	}
}

func (p *TokenMap) RemoveTokenByUserInfo(userInfo UserInfo) {
	p.RWMutex.Lock()
	defer p.RWMutex.Unlock()

	if v, ok := p.u2t[userInfo]; ok {
		delete(p.u2t, userInfo)
		delete(p.t2u, v.Token)
	}
}

// vKey: user
func (p *TokenMap) GetTokenInfo(userInfo UserInfo) (TokenInfo, bool) {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	token, ok := p.u2t[userInfo]

	return token, ok
}

func (p *TokenMap) GetUserInfo(token string) (UserInfo, bool) {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	user, ok := p.t2u[token]

	return user, ok
}

func (p *TokenMap) Dump() (str string) {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	str += fmt.Sprintln("TokenMap:")
	str += fmt.Sprintln("t2u: ---")
	for k, v := range p.t2u {
		str += fmt.Sprintf("token: %s, svc id: %s\n", k, v)
	}
	str += fmt.Sprintln("u2t: ---")
	for k, v := range p.u2t {
		str += fmt.Sprintf("user: %s, tokeninfo: %s\n", k, v)
	}

	return str
}
